﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using SSG.Model;

namespace SSG.Tools
{
    /// <summary>
    /// Basically helpers for KML
    /// </summary>
    //  https://developers.google.com/kml/documentation/kml_tut#placemarks
    public static class Kml
    {
        private const string Highlight = "highlight";
        private const string Normal = "normal";

        /// <summary>
        /// Indention strings as per level
        /// </summary>
        private static readonly Dictionary<int, string> IndentionStrings = new Dictionary<int, string>();
        private const string IndentionString = "  ";

        /// <summary>
        /// Wrap content in document
        /// </summary>
        /// <param name="name"></param>
        /// <param name="content"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        public static string WrapInDocument(string name, string content, int level = 0)
        {
            string ind = Indention(level);
            string ind1 = Indention(level + 1);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            sb.Append(ind);
            sb.Append("<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n");
            sb.Append(ind);
            sb.Append("<Document>\n");
            if (!String.IsNullOrEmpty(name)) {
                sb.Append(ind1);
                sb.Append("<name>");
                sb.Append(name);
                sb.Append("</name>\n");
            }
            sb.Append(IndentString(content, level + 1));
            sb.Append(ind);
            sb.Append("</Document>\n");
            sb.Append(ind);
            sb.Append("</kml>");
            return sb.ToString();
        }

        /// <summary>
        /// Wrap content in document
        /// </summary>
        /// <param name="description"></param>
        /// <param name="content"></param>
        /// <param name="level"></param>
        /// <param name="name"></param>
        /// <param name="open"></param>
        /// <param name="visible"></param>
        /// <returns></returns>
        public static string KmlWrapInFolder(string name, string description, string content, int level = 0, bool open = false, bool visible = false)
        {
            // https://developers.google.com/kml/documentation/kmlreference#folder
            string ind = Indention(level);
            string ind1 = Indention(level + 1);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<Folder>\n");
            sb.Append(ind1);
            sb.Append("<name>");
            sb.Append(name);
            sb.Append("</name>\n");
            sb.Append(ind1);
            sb.Append("<open>");
            sb.Append(open ? "1" : "0");
            sb.Append("</open>\n");
            sb.Append(ind1);
            sb.Append("<visibility>");
            sb.Append(visible ? "1" : "0");
            sb.Append("</visibility>\n");
            if (!String.IsNullOrEmpty(description)) {
                sb.Append(ind1);
                sb.Append("<description>");
                sb.Append(description);
                sb.Append("</description>\n");
            }
            sb.Append(IndentString(content, level + 1));
            sb.Append(ind);
            sb.Append("</Folder>\n");
            return sb.ToString();
        }

        /// <summary>
        /// Indention strings
        /// </summary>
        /// <param name="level"></param>
        /// <returns></returns>
        public static string Indention(int level)
        {
            if (level < 1) return "";
            if (Kml.IndentionStrings.ContainsKey(level)) return Kml.IndentionStrings[level];
            string ind = String.Concat(Enumerable.Repeat(Kml.IndentionString, level));
            Kml.IndentionStrings.Add(level, ind);
            return ind;
        }

        /// <summary>
        /// Indent a text block
        /// </summary>
        /// <param name="content"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        public static string IndentString(string content, int level)
        {
            if (String.IsNullOrEmpty(content)) return null;
            string ind = Indention(level);
            string replaced = Regex.Replace(content, @"^\s*", ind);
            return replaced;
        }

        /// <summary>
        /// Create a placemark
        /// </summary>
        /// <param name="name"></param>
        /// <param name="description"></param>
        /// <param name="styleId"></param>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        /// <param name="level"></param>
        /// <param name="visible"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public static string Placemark(string name, string description, string styleId, double lat, double lng, int level, bool visible, GeneratorSettings settings)
        {
            if (String.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            string ind = Indention(level);
            string ind1 = Indention(level + 1);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<Placemark>\n");
            sb.Append(ind1);
            sb.Append("<name>");
            sb.Append(name);
            sb.Append("</name>\n");
            sb.Append(ind1);
            sb.Append("<visibility>");
            sb.Append(visible ? "1" : "0");
            sb.Append("</visibility>\n");
            // description
            if (!string.IsNullOrEmpty(description)) {
                sb.Append(ind1);
                sb.Append("<description>");
                sb.Append(description);
                sb.Append("</description>\n");
            }
            // style
            if (!string.IsNullOrEmpty(styleId)) {
                sb.Append(ind1);
                sb.Append("<styleUrl>");
                if (!styleId.StartsWith("#")) sb.Append("#");
                sb.Append(styleId);
                sb.Append("</styleUrl>\n");
            }
            sb.Append(Point(lat, lng, level + 1, settings));
            sb.Append(ind);
            sb.Append("</Placemark>\n");
            return sb.ToString();
        }

        /// <summary>
        /// Create a single point
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        /// <param name="level"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public static string Point(double lat, double lng, int level, GeneratorSettings settings)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            string latStr = String.Format(CultureInfo.InvariantCulture, "{0:0.00000000}", lat);
            string lngStr = String.Format(CultureInfo.InvariantCulture, "{0:0.00000000}", lng);
            sb.Append("<Point><coordinates>");
            sb.Append(lngStr);
            sb.Append(",");
            sb.Append(latStr);
            sb.Append("</coordinates></Point>\n");
            return sb.ToString();
        }

        /// <summary>
        /// Create a single point
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        /// <param name="altitude"></param>
        /// <param name="level"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public static string Coordinates(double lat, double lng, double altitude, int level, GeneratorSettings settings)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            string latStr = String.Format(CultureInfo.InvariantCulture, "{0:0.00000000}", lat);
            string lngStr = String.Format(CultureInfo.InvariantCulture, "{0:0.00000000}", lng);
            string altStr = String.Format(CultureInfo.InvariantCulture, "{0:0.00}", altitude);
            sb.Append(lngStr);
            sb.Append(",");
            sb.Append(latStr);
            sb.Append(",");
            sb.Append(altStr);
            return sb.ToString();
        }

        /// <summary>
        /// All navicon styles
        /// </summary>
        /// <param name="level"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public static string DocumentStyles(int level, GeneratorSettings settings)
        {
            // https://developers.google.com/kml/documentation/kmlreference?hl=de#iconstyle
            if (settings == null) throw new ArgumentNullException("settings");
            StringBuilder sb = new StringBuilder();
            sb.Append(Kml.AppendStyleAndStyleMap(Fix.KmlPlacemarkStyle, settings.SymbolWaypointUrl, 0.75, 1.0, level, settings));
            sb.Append(Kml.AppendStyleAndStyleMap(Runway.KmlPlacemarkStyle, settings.SymbolRunwayUrl, 0.75, 1.0, level, settings));
            sb.Append(Kml.AppendStyleAndStyleMap(Leg.KmlPlacemarkStyle, settings.SymbolLegUrl, 0.75, 1.0, level, settings));

            IEnumerable<Navaid.NavaidType> navaidTypes = Enum.GetValues(typeof (Navaid.NavaidType)).Cast<Navaid.NavaidType>();
            foreach (Navaid.NavaidType navaidType in navaidTypes) {
                string styleId = Navaid.TypeToKmlStyleid(navaidType);
                string iconUrl = Navaid.TypeToSvgUrl(navaidType, settings);
                sb.Append(Kml.AppendStyleAndStyleMap(styleId, iconUrl, 0.75, 1.0, level, settings));
            }

            // Line string
            sb.Append(FilledLineStyle(typeof (Sid), level, settings));
            sb.Append(FilledLineStyle(typeof (Star), level, settings));
            sb.Append(FilledLineStyle(typeof (Transition), level, settings));
            sb.Append(FilledLineStyle(typeof (Approach), level, settings));

            // bye
            return sb.ToString();
        }

        /// <summary>
        /// Append style and style map
        /// </summary>
        /// <param name="styleId"></param>
        /// <param name="iconUrl"></param>
        /// <param name="scaleNormal"></param>
        /// <param name="scaleHighlight"></param>
        /// <param name="level"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        private static string AppendStyleAndStyleMap(string styleId, string iconUrl, double scaleNormal, double scaleHighlight, int level, GeneratorSettings settings)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(PlacemarkStyle(styleId + "." + Normal, iconUrl, scaleNormal, scaleNormal, level, settings));
            sb.Append(PlacemarkStyle(styleId + "." + Highlight, iconUrl, scaleHighlight, scaleHighlight, level, settings));
            sb.Append(PlacemarkStyleMap(styleId, Normal, Highlight, level));
            return sb.ToString();
        }

        /// <summary>
        /// KML for one placemark style
        /// </summary>
        /// <param name="styleId"></param>
        /// <param name="iconUrl"></param>
        /// <param name="iconScale"></param>
        /// <param name="labelScale"></param>
        /// <param name="level"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        private static string PlacemarkStyle(string styleId, string iconUrl, double iconScale, double labelScale, int level, GeneratorSettings settings)
        {
            string ind = Indention(level);
            string ind1 = Indention(level + 1);
            string ind2 = Indention(level + 2);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<Style id=\"");
            sb.Append(styleId);
            sb.Append("\">\n");
            sb.Append(ind1);
            sb.Append("<IconStyle>\n");
            sb.Append(iconScale.KmlScale(level + 2, "\n"));
            sb.Append(settings.KmlIconColour.KmlColor(level + 2, "\n"));
            sb.Append(ind2);
            sb.Append("<Icon>\n");
            sb.Append(iconUrl.KmlHref(level + 3, "\n"));
            // I have tried to set icons size here, with no effect in the style
            // sb.Append(Kml.KmlGraphicsHw(iconSize, iconSize, level + 3, "\n"));
            sb.Append(ind2);
            sb.Append("</Icon>\n");
            sb.Append(ind1);
            sb.Append("</IconStyle>\n");
            sb.Append(ind1);
            sb.Append("<LabelStyle>\n");
            sb.Append(labelScale.KmlScale(level + 2, "\n"));
            sb.Append(settings.KmlLabelColour.KmlColor(level + 2, "\n"));
            sb.Append(ind1);
            sb.Append("</LabelStyle>\n");
            sb.Append(ind);
            sb.Append("</Style>\n");
            return sb.ToString();
        }

        /// <summary>
        /// KML for one placemark style
        /// </summary>
        /// <param name="type"></param>
        /// <param name="level"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        private static string FilledLineStyle(Type type, int level, GeneratorSettings settings)
        {
            string styleId = Procedure.KmlTrackStyle + "." + type.GetClassname();
            string ind = Indention(level);
            string ind1 = Indention(level + 1);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<Style id=\"");
            sb.Append(styleId);
            sb.Append("\">\n");
            sb.Append(ind1);
            sb.Append("<LineStyle>\n");
            sb.Append(settings.GetKmlColorAbgrHex(type).KmlColor(level + 2, "\n"));
            sb.Append(settings.KmlLineWidth.KmlWidth(level + 2, "\n"));
            sb.Append(ind1);
            sb.Append("</LineStyle>\n");
            sb.Append(ind1);
            sb.Append("<PolyStyle>\n");
            sb.Append(settings.GetKmlFillColorAbgrHex(type).KmlColor(level + 2, "\n"));
            sb.Append(true.KmlOutline(level + 2, "\n"));
            sb.Append(ind1);
            sb.Append("</PolyStyle>\n");
            sb.Append(ind);
            sb.Append("</Style>\n");
            return sb.ToString();
        }

        /// <summary>
        /// Style map
        /// </summary>
        /// <param name="styleId"></param>
        /// <param name="keyNormal"></param>
        /// <param name="keyHighlight"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        private static string PlacemarkStyleMap(this string styleId, string keyNormal, string keyHighlight, int level)
        {
            // https://developers.google.com/kml/documentation/kmlreference?hl=de#stylemap
            string ind = Indention(level);
            string ind1 = Indention(level + 1);
            string styleUrlNormal = styleId + "." + keyNormal;
            string styleUrlHighlight = styleId + "." + keyHighlight;
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<StyleMap id=\"");
            sb.Append(styleId);
            sb.Append("\">\n");
            sb.Append(ind1);
            sb.Append("<Pair>\n");
            sb.Append(styleUrlNormal.KmlStyleUrl(level + 2, "\n"));
            sb.Append(keyNormal.KmlKey(level + 2, "\n"));
            sb.Append(ind1);
            sb.Append("</Pair>\n");
            sb.Append(ind1);
            sb.Append("<Pair>\n");
            sb.Append(styleUrlHighlight.KmlStyleUrl(level + 2, "\n"));
            sb.Append(keyHighlight.KmlKey(level + 2, "\n"));
            sb.Append(ind1);
            sb.Append("</Pair>\n");

            sb.Append(ind);
            sb.Append("</StyleMap>\n");
            return sb.ToString();
        }

        /// <summary>
        /// Name
        /// </summary>
        /// <param name="name"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlName(this string name, int level, String end = null)
        {
            if (String.IsNullOrEmpty(name)) return "";
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<name>");
            sb.Append(name);
            sb.Append("</name>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Color
        /// </summary>
        /// <param name="color"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlColor(this string color, int level, String end = null)
        {
            if (String.IsNullOrEmpty(color)) return "";
            string ind = Indention(level);
            string cs = color.ToLowerInvariant().Replace("#", String.Empty);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<color>");
            sb.Append(cs);
            sb.Append("</color>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Width
        /// </summary>
        /// <param name="width"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlWidth(this int width, int level, String end = null)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<width>");
            sb.Append(width);
            sb.Append("</width>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Visibilty
        /// </summary>
        /// <param name="visibility"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlVisibility(this bool visibility, int level, String end = null)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<visibility>");
            sb.Append((visibility ? "1" : "0"));
            sb.Append("</visibility>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Extrude
        /// </summary>
        /// <param name="extrude"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlExtrude(this bool extrude, int level, String end = null)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<extrude>");
            sb.Append((extrude ? "1" : "0"));
            sb.Append("</extrude>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Outline
        /// </summary>
        /// <param name="outline"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlOutline(this bool outline, int level, String end = null)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<outline>");
            sb.Append((outline ? "1" : "0"));
            sb.Append("</outline>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Height and width
        /// </summary>
        /// <param name="w"></param>
        /// <param name="h"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlGraphicsHw(int w, int h, int level, String end = null)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<gx:w>");
            sb.Append(w);
            sb.Append("</gx:w>");
            sb.Append("<gx:h>");
            sb.Append(h);
            sb.Append("</gx:h>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Element href
        /// </summary>
        /// <param name="url"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlHref(this string url, int level, String end = null)
        {
            if (String.IsNullOrEmpty(url)) return "";
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<href>");
            sb.Append(url);
            sb.Append("</href>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Style
        /// </summary>
        /// <param name="url"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlStyleUrl(this string url, int level, String end = null)
        {
            if (String.IsNullOrEmpty(url)) return "";
            if (!url.Contains("://") && !url.StartsWith("#")) url = "#" + url;
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<styleUrl>");
            sb.Append(url);
            sb.Append("</styleUrl>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Key
        /// </summary>
        /// <param name="key"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlKey(this string key, int level, String end = null)
        {
            if (String.IsNullOrEmpty(key)) return "";
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<key>");
            sb.Append(key);
            sb.Append("</key>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }

        /// <summary>
        /// Scale
        /// </summary>
        /// <param name="scale"></param>
        /// <param name="level"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static string KmlScale(this double scale, int level, String end = null)
        {
            string ind = Indention(level);
            StringBuilder sb = new StringBuilder(ind);
            sb.Append("<scale>");
            sb.Append(scale.ToString("0.00", CultureInfo.InvariantCulture));
            sb.Append("</scale>");
            if (!String.IsNullOrEmpty(end)) sb.Append(end);
            return sb.ToString();
        }
    }
}